﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using InfiniTec;
using InfiniTec.Exchange.Notifications;
using InfiniTec.Threading;

namespace CalendarSubscriptionApp
{
	public sealed class SubscriptionManager
    {
		[field: NonSerialized]
		public event EventHandler<AutodiscoverFailedEventArgs> AutodiscoverFailed;

		[field: NonSerialized]
		public event EventHandler<AutodiscoverRedirectedEventArgs> AutodiscoverRedirected;

        private const string DefaultCacheFilename = "subscriptions.cache";
        private readonly ExchangeConnector _ExchangeWebServiceAdapter;
        private readonly PushNotificationListener _PushNotificationListener;
        private readonly List<CalendarSubscription> _CalendarSubscriptions = new List<CalendarSubscription>();
        private SubscriptionCollection _Subscriptions;
        private DispatcherCookie _StateChangedCookie;
        private object _StateChangedLock = new object();
        private readonly AppointmentsWindowViewModel _AppointmentsViewModel;

        public SubscriptionManager()
        {
            ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
            _ExchangeWebServiceAdapter = new ExchangeConnector();
            _ExchangeWebServiceAdapter.AutodiscoverFailed += ((sender, e) => AutodiscoverFailed.Raise(this, e));
        	_ExchangeWebServiceAdapter.AutoDiscoverRedirected += ((sender, e) => AutodiscoverRedirected.Raise(this, e));

            _PushNotificationListener = new PushNotificationListener();
            _PushNotificationListener.Start();

            string filename = GetApplicationCacheFilename(false);

            if (File.Exists(filename))
            {
                DeserializeApplicationState(filename);
                _Subscriptions.RestartAsync();
            }
            if (_Subscriptions == null)
            {
                _Subscriptions = new SubscriptionCollection(_ExchangeWebServiceAdapter, _PushNotificationListener);
            }

            _AppointmentsViewModel = new AppointmentsWindowViewModel(new SubscriptionManagerViewModel(this));
            _AppointmentsViewModel.ShowAppointmentsWindow();
        }

		private static string GetApplicationCacheFilename(bool createConfigurationDirectory)
        {
            string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CalendarSubscriptionApp");

            if (createConfigurationDirectory && !Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            return Path.Combine(path, DefaultCacheFilename);
        }

        public IEnumerable<CalendarSubscription> CalendarSubscriptions
        {
            get { return _CalendarSubscriptions; }
        }

		private void SaveApplicationState()
		{
			using (FileStream stream = File.OpenWrite(GetApplicationCacheFilename(true)))
			{
				_Subscriptions.Serialize(stream);
				var writer = new BinaryFormatter();

				writer.Serialize(stream, _CalendarSubscriptions.ToList());
			}
		}

		private void DeserializeApplicationState(string filename)
        {
            using (FileStream stream = File.OpenRead(filename))
            {
                _Subscriptions = SubscriptionCollection.Deserialize(stream, _ExchangeWebServiceAdapter, _PushNotificationListener);

                var reader = new BinaryFormatter();
                var list = (List<CalendarSubscription>) reader.Deserialize(stream);
                _CalendarSubscriptions.AddRange(list);
            }
            var items = from subscription in _Subscriptions
                        join calendarSubscription in _CalendarSubscriptions on subscription.Folders.First().Mailbox.Address equals calendarSubscription.MailboxId
                        select new {Subscription = subscription, CalendarSubscription = calendarSubscription};

			foreach (var item in items)
			{
				item.CalendarSubscription.InitializeAfterDeserialization(item.Subscription, _ExchangeWebServiceAdapter, this);
				item.CalendarSubscription.NotificationReceived += CalendarSubscriptionNotificationReceived;
			}
        }

        private void CalendarSubscriptionNotificationReceived(object sender, EventArgs args)
        {
        	ScheduleSaveApplicationState();
        }

    	private void ScheduleSaveApplicationState()
    	{
    		lock (_StateChangedLock)
    		{
    			if (_StateChangedCookie == null)
    			{
    				_StateChangedLock = Dispatcher.RegisterCallback(TimeSpan.FromSeconds(10), state =>
    				                                                                          	{
    				                                                                          		lock (_StateChangedLock)
    				                                                                          		{
    				                                                                          			_StateChangedCookie = null;
    				                                                                          		}
    				                                                                          		SaveApplicationState();
    				                                                                          	}, null);
    			}
    		}
    	}

    
        public CalendarSubscription SubscribeToCalendar(string primaryEmailAddress)
        {
			if (string.IsNullOrEmpty(primaryEmailAddress)) throw new ArgumentNullException("primaryEmailAddress");

            Subscription subscription = _Subscriptions.Add(new FolderReference(WellKnownFolderId.Calendar, new MailboxReference(primaryEmailAddress)).AsEnumerable(), EventTypes.Created | EventTypes.Deleted | EventTypes.Modified | EventTypes.Moved, SubscriptionType.Push);
            var calendarSubscription = new CalendarSubscription(subscription, _ExchangeWebServiceAdapter, this);

			_CalendarSubscriptions.Add(calendarSubscription);
            calendarSubscription.NotificationReceived += CalendarSubscriptionNotificationReceived;

        	return calendarSubscription;
        }

		public void RemoveSubscription(CalendarSubscription subscription)
		{
			_CalendarSubscriptions.Remove(subscription);
			_Subscriptions.Remove(subscription.Subscription);
		}
    }
}